/**
 * HttpClient.java
 * 
 * Create Version: 1.0
 * Author: 刘洪政
 * Create Date: 2012-4-23
 */
package ysyh55.android.base.http;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.http.HttpStatus;
import org.apache.http.HttpVersion;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.conn.params.ConnManagerParams;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.HTTP;

import android.util.Log;

import ysyh55.android.base.exception.HttpAuthException;
import ysyh55.android.base.exception.HttpException;
import ysyh55.android.base.exception.HttpRefusedException;
import ysyh55.android.base.exception.HttpResponseException;
import ysyh55.android.base.exception.HttpServerException;

/**
 * HttpClient HTTP 客户端封装类
 * 
 * @author 刘洪政
 */
public class HttpClient {
	private static final String TAG = "HttpClient";
	private static final int CONNECTION_TIMEOUT_MS = 20 * 1000;
	private static final int SOCKET_TIMEOUT_MS = 20 * 1000;
	private static final int SOCKET_BUFFER_SIZE = 8192;

	private DefaultHttpClient client;

	public DefaultHttpClient getClient() {
  	return client;
  }

	/**
	 * 默认认证参数MAP
	 */
	private Map<String, String> defaultAuthParam;

	public HttpClient() {
		initClient();
	}

	public HttpResponse get(final String url) throws HttpException, IOException {
		return connect(url, null, HttpGet.METHOD_NAME);
	}

	public HttpResponse get(final String url, final Map<String, String> params) throws HttpException, IOException {
		return connect(url, params, HttpGet.METHOD_NAME);
	}

	public HttpResponse post(final String url) throws HttpException, IOException {
		return connect(url, null, HttpPost.METHOD_NAME);
	}

	public HttpResponse post(final String url, final Map<String, String> params) throws HttpException, IOException {
		return connect(url, params, HttpPost.METHOD_NAME);
	}

	public HttpResponse connect(final String url, final Map<String, String> params, String method) throws IOException,
			HttpException {
		HttpUriRequest request = getRequest(url, params, method);
		HttpResponse rsp = null;

		org.apache.http.HttpResponse httpResponse;
		try {
			httpResponse = client.execute(request);
			rsp = new HttpResponse(httpResponse);
		} catch (ClientProtocolException e) {
			Log.e(TAG, e.getMessage(), e);
			throw new HttpResponseException(e.getMessage(), e);
		}

		if (httpResponse != null) {
			int statusCode = httpResponse.getStatusLine().getStatusCode();
			handleHttpResponseStatusCode(statusCode, rsp);
		} else {
			Log.e(TAG, "HttpHttpResponse is null!");
		}

		return rsp;
	}

	protected void initClient() {
		final HttpParams params = new BasicHttpParams();
		ConnManagerParams.setMaxTotalConnections(params, 5);
		HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
		HttpProtocolParams.setContentCharset(params, "UTF-8");

		HttpConnectionParams.setStaleCheckingEnabled(params, false);
		HttpConnectionParams.setConnectionTimeout(params, CONNECTION_TIMEOUT_MS);
		HttpConnectionParams.setSoTimeout(params, SOCKET_TIMEOUT_MS);
		HttpConnectionParams.setSocketBufferSize(params, SOCKET_BUFFER_SIZE);

		HttpClientParams.setRedirecting(params, false);

		client = new DefaultHttpClient(params);
	}

	protected HttpUriRequest getRequest(String url, Map<String, String> params, String method) throws HttpException {
		if (params == null) {
			params = new HashMap<String, String>();
		}

		if (method.equalsIgnoreCase(HttpPost.METHOD_NAME)) {
			List<NameValuePair> listParams = new ArrayList<NameValuePair>();

			if (this.defaultAuthParam != null && !this.defaultAuthParam.isEmpty()) {
				for (String key : this.defaultAuthParam.keySet()) {
					if (!params.containsKey(key)) {
						params.put(key, this.defaultAuthParam.get(key));
					}
				}
			}

			for (String name : params.keySet()) {
				listParams.add(new BasicNameValuePair(name, params.get(name)));
			}

			try {
				UrlEncodedFormEntity entity = new UrlEncodedFormEntity(listParams, HTTP.UTF_8);
				HttpPost request = new HttpPost(url);
				request.setEntity(entity);

				return request;
			} catch (UnsupportedEncodingException e) {
				Log.e(TAG, e.getMessage(), e);

				throw new HttpResponseException(e.getMessage(), e);
			}
		} else {
			if (url.indexOf("?") < 0) {
				url += "?";
			}

			try {
				for (String name : params.keySet()) {
					url += "&" + name + "=" + URLEncoder.encode(params.get(name), HTTP.UTF_8);
				}
			} catch (UnsupportedEncodingException e) {
				Log.e(TAG, e.getMessage(), e);
				throw new HttpResponseException(e.getMessage(), e);
			}

			if (method.equalsIgnoreCase(HttpDelete.METHOD_NAME)) {
				return new HttpDelete(url);
			} else {
				return new HttpGet(url);
			}
		}
	}

	protected void handleHttpResponseStatusCode(int statusCode, HttpResponse rsp) throws HttpException, IOException {
		String msg = getCause(statusCode) + "\n";

		switch (statusCode) {
		case HttpStatus.SC_OK:
			break;
		case HttpStatus.SC_NOT_MODIFIED:
		case HttpStatus.SC_BAD_REQUEST:
		case HttpStatus.SC_NOT_FOUND:
		case HttpStatus.SC_NOT_ACCEPTABLE:
			throw new HttpException(msg + rsp.asString(), statusCode);
		case HttpStatus.SC_UNAUTHORIZED:
			throw new HttpAuthException(msg + rsp.asString(), statusCode);
		case HttpStatus.SC_FORBIDDEN:
			throw new HttpRefusedException(msg, statusCode);
		case HttpStatus.SC_INTERNAL_SERVER_ERROR:
		case HttpStatus.SC_BAD_GATEWAY:
		case HttpStatus.SC_SERVICE_UNAVAILABLE:
			throw new HttpServerException(msg, statusCode);
		default:
			throw new HttpException(msg + rsp.asString(), statusCode);
		}
	}

	private static String getCause(int statusCode) {
		String cause = null;
		switch (statusCode) {
		case HttpStatus.SC_NOT_MODIFIED:
			break;
		case HttpStatus.SC_BAD_REQUEST:
			cause = "The request was invalid.  An accompanying error message will explain why. This is the status code will be returned during rate limiting.";
			break;
		case HttpStatus.SC_UNAUTHORIZED:
			cause = "Authentication credentials were missing or incorrect.";
			break;
		case HttpStatus.SC_FORBIDDEN:
			cause = "The request is understood, but it has been refused.  An accompanying error message will explain why.";
			break;
		case HttpStatus.SC_NOT_FOUND:
			cause = "The URI requested is invalid or the resource requested, such as a user, does not exists.";
			break;
		case HttpStatus.SC_NOT_ACCEPTABLE:
			cause = "Returned by the Search API when an invalid format is specified in the request.";
			break;
		case HttpStatus.SC_INTERNAL_SERVER_ERROR:
			cause = "Something is broken.  Please post to the group so the webmaster team can investigate.";
			break;
		case HttpStatus.SC_BAD_GATEWAY:
			cause = "Server is down or being upgraded.";
			break;
		case HttpStatus.SC_SERVICE_UNAVAILABLE:
			cause = "Service Unavailable: The servers are up, but overloaded with requests. Try again later. The search and trend methods use this to indicate when you are being rate limited.";
			break;
		default:
			cause = "";
		}
		return statusCode + ":" + cause;
	}

	public void setDefaultAuthMap(Map<String, String> defaultAuthMap) {
		this.defaultAuthParam = defaultAuthMap;
	}
}
